T007 自定义控件 图文弹跳加载中效果

要想实现上述效果,需要准备三张图:

loading_text_1.png

loading_text_2.png

loading_text_3.png

然后让控件派生自 ImageView 类,这样才能方便地更改它的源文件内容。

想让图片上下跳动,可以利用 ValueAnimator 实时产生一个 0~200 的数值,然后让当前图片的位置实时向上移动 ValueAnimator 的动态值的高度即可。要让图片的位置实时向上移动,就需要先拿到初始状态下图片的位置。重写 onLayout(boolean changed, int left, int top, int right, int bottom) 函数,就可以拿到控件的初始高度 mTop,之后在每次 ValueAnimator 的动态值到来时,计算出当前控件的 top 位置,并将控件移动到这个位置就可以了。

即:自定义一个控件 LoadingImageView, 派生自 ImageView,然后重写 onLayout() 函数,拿到控件的初始 top 值。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class LoadingImageView extends AppCompatImageView {
// 初始 top
private int mTop;
// 当前动画图片索引
private int mCurImgIndex = 0;
// 动画图片总张数
private static int mImgCount = 3;
public LoadingImageView(Context context) {
super(context);
init();
}
public LoadingImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LoadingImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
...
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mTop = top;
}
}

由于我们需要在刚展示图片时就开始动画,所以将动画的操作全部写在 init() 函数中。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void init() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 200, 0);
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new AccelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer dx = (Integer) animation.getAnimatedValue();
setTop(mTop - dx);
}
});
...
}

通过 (mTop - dx) 得到当前控件相对初始坐标上移 dx 距离后的最新坐标点,然后调用 setTop(int top) 函数将控件移动到当前位置。

接下来需要监听动画的开始和重复。当动画开始时,图片应该设置为 loading_text_1.png;在重复时,每重复一次应该更换一张图片。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 当前动画图片索引
private int mCurImgIndex = 0;
// 动画图片总张数
private static int mImgCount = 3;
private void init() {
...
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
setImageDrawable(getResources().getDrawable(R.drawable.loading_text_1));
}
@Override
public void onAnimationEnd(Animator animation) {}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {
mCurImgIndex++;
switch (mCurImgIndex % mImgCount) {
case 0:
setImageDrawable(getResources().getDrawable(R.drawable.loading_text_1));
break;
case 1:
setImageDrawable(getResources().getDrawable(R.drawable.loading_text_2));
break;
case 2:
setImageDrawable(getResources().getDrawable(R.drawable.loading_text_3));
break;
}
}
});
valueAnimator.start();
}

在更改图片时,我们使用 mCurImgIndex 来累加当前重复的次数,通过与图片总张数 (mImgCount) 取余数,来决定这次重复使用的是哪张图片。

使用控件:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.xxt.xtest.LoadingImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="50dp"/>
</LinearLayout>

LoadingImageView 的完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.xxt.xtest;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.animation.AccelerateInterpolator;
import androidx.appcompat.widget.AppCompatImageView;
public class LoadingImageView extends AppCompatImageView {
private int mTop;
// 当前动画图片索引
private int mCurImgIndex = 0;
// 动画图片总张数
private static int mImgCount = 3;
public LoadingImageView(Context context) {
super(context);
init();
}
public LoadingImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LoadingImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 200, 0);
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new AccelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer dx = (Integer) animation.getAnimatedValue();
setTop(mTop - dx);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
setImageDrawable(getResources().getDrawable(R.drawable.loading_text_1));
}
@Override
public void onAnimationEnd(Animator animation) {}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {
mCurImgIndex++;
switch (mCurImgIndex % mImgCount) {
case 0:
setImageDrawable(getResources().getDrawable(R.drawable.loading_text_1));
break;
case 1:
setImageDrawable(getResources().getDrawable(R.drawable.loading_text_2));
break;
case 2:
setImageDrawable(getResources().getDrawable(R.drawable.loading_text_3));
break;
}
}
});
valueAnimator.start();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mTop = top;
}
}